package sk.stuba.fiit.clientApp.service.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.UnrecoverableEntryException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.springframework.context.ApplicationContext;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.stereotype.Service;
import org.w3._2000._09.xmldsig.ReferenceType;

import sk.stuba.fiit.clientApp.cvo.UserDetailCVO;
import sk.stuba.fiit.clientApp.service.StorageService;
import sk.stuba.fiit.clientApp.singeltons.ApplicationContextSingelton;
import sk.stuba.fiit.clientApp.util.InitializationUtil;
import sk.stuba.fiit.ltasys.ws.schema.beans.SignatureRequest;


@Service("storageService")
public class StorageServiceImpl implements StorageService{

	private final static String  JAXB_BEAN = "marshaller-client";

	public static ApplicationContext singelton = ApplicationContextSingelton.getInstance();
	public static Jaxb2Marshaller controler =  (Jaxb2Marshaller) singelton.getBean(JAXB_BEAN);
	
	public void storeXMLSignature(SignatureRequest signature, String outputFile, List<File> fileName) throws IOException{
		File signFile = new File("Signature.xml");
		Result result = new StreamResult(signFile); 
		controler.marshal(signature, result);
		fileName.add(signFile);
		makeZipFromFiles(fileName,outputFile);
		signFile.delete();
	}
	
	private  File makeZipFromFiles(List<File> file, String outputFile) throws IOException {
		byte[] buf = new byte[1024];

		ZipOutputStream out = new ZipOutputStream(new FileOutputStream(outputFile));

		for (int i = 0; i < file.size(); i++) {
			FileInputStream in = new FileInputStream(file.get(i));
			out.putNextEntry(new ZipEntry(file.get(i).getName()));
			int len;
			while ((len = in.read(buf)) > 0) {
				out.write(buf, 0, len);
			}

			out.closeEntry();
			in.close();
		}
		out.close();
		return new File(outputFile);
	}
	
	public byte[] readFile(File file) throws IOException{
		InputStream is = new FileInputStream(file);
	    long length = file.length();
	    if (length > Integer.MAX_VALUE) {
	        throw new IOException("File is too large");
	    }
	    byte[] bytes = new byte[(int)length];
	    int offset = 0;
	    int numRead = 0;
	    while (offset < bytes.length
	           && (numRead=is.read(bytes, offset, Math.min(bytes.length - offset, 512*1024))) >= 0) {
	        offset += numRead;
	    }
	    if (offset < bytes.length) {
	        throw new IOException("Could not completely read file "+file.getName());
	    }
	    
	    is.close();
	    return bytes;
	}
	
	public PrivateKey loadPrivateKey(String storageType, String fileName,
			String password) throws KeyStoreException, IOException,
			NoSuchAlgorithmException, CertificateException,
			UnrecoverableKeyException {
		KeyStore ks = KeyStore.getInstance(storageType);
		FileInputStream fis = new FileInputStream(new File(fileName));
		ks.load(fis, password.toCharArray());
		fis.close();
		PrivateKey key = (PrivateKey) ks
				.getKey("alias", password.toCharArray());
		return key;
	}

	public PrivateKey loadPrivateKeyFromPKCS8(String fileName, String algorithm)
			throws NoSuchAlgorithmException, IOException,
			InvalidKeySpecException {
		if (algorithm == null)
			algorithm = ALGORITHM_RSA;
		KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
		File filePrivateKey = new File(fileName);
		FileInputStream fis = new FileInputStream(filePrivateKey);
		byte[] encodedPrivateKey = new byte[(int) filePrivateKey.length()];
		fis.read(encodedPrivateKey);
		fis.close();
		PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(
				encodedPrivateKey);
		PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
		return privateKey;
	}
	
	public boolean generateAndSaveKeyPair(String path, String algorithm,
			int lenght,String privateKeyName, String publicKeyName) throws NoSuchAlgorithmException,
			NoSuchProviderException {
		SecureRandom rand = new SecureRandom();
		if (algorithm == null) {
			algorithm = ALGORITHM_RSA;
		}
		KeyPairGenerator kpg = KeyPairGenerator.getInstance(algorithm, "BC");
		kpg.initialize(1024, rand);
		try {
			saveKeyPair(path, kpg.generateKeyPair(),publicKeyName,privateKeyName);
			return true;
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
	}

	public KeyPair generateKeyPair(String algorithm, int lenght)
			throws NoSuchAlgorithmException, NoSuchProviderException {
		SecureRandom rand = new SecureRandom();
		if (algorithm == null) {
			algorithm = ALGORITHM_RSA;
		}
		KeyPairGenerator kpg = KeyPairGenerator.getInstance(algorithm, "BC");
		kpg.initialize(1024, rand);
		return kpg.generateKeyPair();
	}

	public void saveKeyPair(String path, KeyPair keyPair,String namePublicKey, String namePrivateKey) throws IOException {
		PrivateKey privateKey = keyPair.getPrivate();
		PublicKey publicKey = keyPair.getPublic();
		
		if(namePublicKey==null){
			namePublicKey = "public.key";
		}
		if(namePrivateKey==null){
			namePrivateKey = "private.key";
		}

		X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(
				publicKey.getEncoded());
		FileOutputStream fos = new FileOutputStream(path + File.separator
				+ namePublicKey);
		fos.write(x509EncodedKeySpec.getEncoded());
		fos.close();

		PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(
				privateKey.getEncoded());
		fos = new FileOutputStream(path + File.separator + namePrivateKey);
		fos.write(pkcs8EncodedKeySpec.getEncoded());
		fos.close();
	}

	public void savePrivateKey(String path, PrivateKey key,String namePrivateKey) throws IOException {
		if(namePrivateKey==null){
			namePrivateKey="private.key";
		}
		FileOutputStream fos = new FileOutputStream(path + File.separator + namePrivateKey);
		PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(
				key.getEncoded());
		fos.write(pkcs8EncodedKeySpec.getEncoded());
		fos.close();
	}
	
	public KeyPair loadKeyPair(String path, String algorithm,String publicKeyName, String privateKeyName)
			throws IOException, NoSuchAlgorithmException,
			InvalidKeySpecException {

		if(publicKeyName == null){
			publicKeyName = "public.key";
		}
		if(privateKeyName==null){
			privateKeyName="private.key";
		}
		
		if (algorithm == null) {
			algorithm = ALGORITHM_RSA;
		}
		File filePublicKey = new File(path + File.separator + publicKeyName);
		FileInputStream fis = new FileInputStream(filePublicKey);
		byte[] encodedPublicKey = new byte[(int) filePublicKey.length()];
		fis.read(encodedPublicKey);
		fis.close();

		File filePrivateKey = new File(path + File.separator + privateKeyName);
		fis = new FileInputStream(filePrivateKey);
		byte[] encodedPrivateKey = new byte[(int) filePrivateKey.length()];
		fis.read(encodedPrivateKey);
		fis.close();

		KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
		X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(
				encodedPublicKey);
		PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);

		PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(
				encodedPrivateKey);
		PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);

		return new KeyPair(publicKey, privateKey);
	}
	
	
	public  X509Certificate loadCertificateFromCertFile(String fileName) throws CertificateException, IOException{
		CertificateFactory factory = CertificateFactory.getInstance("X.509"); 
		FileInputStream fis = new FileInputStream(new File(fileName));
		X509Certificate certificate = (X509Certificate) factory.generateCertificate(fis);  
		fis.close();
		return certificate;
	}

	public X509Certificate loadCertificate(String fileName, String password,String storageType) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, UnrecoverableEntryException{
		if(storageType==null)
			storageType="JKS";
		KeyStore ks = KeyStore.getInstance(storageType);
		FileInputStream fis = new FileInputStream(new File(fileName));
		if(password==null){
			ks.load(fis,null);
		}else{
			ks.load(fis,password.toCharArray());
		}
		fis.close();
		X509Certificate cert = (X509Certificate) ks.getCertificate("alias");
		return cert;
	}
	
	public boolean storeCertificate(String storageType,String password,X509Certificate certificate,String path,String name){
		if(storageType==null)
			storageType="jks";
		if(name == null){
			name = "cert."+storageType;
		}else{
			name = name+"."+storageType;
		}
        KeyStore keystore;
		try {
			keystore = KeyStore.getInstance(storageType);
			keystore.load( null, null );  
			keystore.setCertificateEntry("alias", certificate);
			FileOutputStream fos = new FileOutputStream(new File(path+File.separator+name));
			if(password!=null){
				keystore.store(fos, password.toCharArray());
			}else{
				keystore.store(fos, null);				
			}
			fos.close();
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}
	
	public boolean storeCertificateWithPrivateKey(String path,String name,PrivateKey key,X509Certificate[] chain,String password){
		try {
			if(name == null){
				name = "CertWitKey.jks";
			}
			File file=	new File(path+File.separator+name);
			if(!file.exists())
				file.createNewFile();
			KeyStore ks = KeyStore.getInstance("JKS");
			ks.load(null, null);
			
			KeyStore.PrivateKeyEntry privateKeyEntry = new KeyStore.PrivateKeyEntry(key,chain);
			ks.setEntry("alias", privateKeyEntry, new KeyStore.PasswordProtection(password.toCharArray()));
			FileOutputStream fos=  new FileOutputStream(file);
			ks.store(fos, password.toCharArray());
			fos.close();
			return true;
		}catch(Exception e){
			e.printStackTrace();
			return false;
		}		
	}

	@Override
	public List<File> findFileBySignature(String path) throws Exception {
	    Source source = new StreamSource(new File(path));
	    SignatureRequest archiveRequest = (SignatureRequest)SignerServiceImpl.controler.unmarshal(source);
		List<ReferenceType> reference = archiveRequest.getXades().get(0).getSignature().getSignedInfo().getReference();
		File filePackage = new File(path);
		File[] files = filePackage.getParentFile().listFiles();
		List<File> result = new ArrayList<File>();
		for(int i = 0 ; i< files.length; i++){
			for (ReferenceType ref : reference) {
				if(ref.getURI().equals(files[i].getName()))
					result.add(files[i]);
			}	
		}
		return result;
	}

	@Override
	public UserDetailCVO setUserAccount(String name, String password, String id) {
		try{
			InitializationUtil.setUserDetail(name, password, id);
		}catch(Exception e){
			e.printStackTrace();
		}
		UserDetailCVO result = new UserDetailCVO();
		result.setUserId(id);
		result.setUsername(name);
		result.setUserPassword(password);
		return result;
	}

	@Override
	public void saveBinaryData(File file, byte[] data) throws Exception {
		 FileOutputStream fos = new FileOutputStream(file);
		 fos.write(data);
		 fos.close();
	}
	
	@Override
	public byte[] loadBinaryData(File file) throws Exception{
		byte[] result = new byte[(int) file.length()];
		FileInputStream fis = new FileInputStream(file);
		fis.read(result);
		return result;
	}
	
}
